/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package navigators.smart.tom.demo.listvalue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import navigators.smart.tom.MessageContext;
import navigators.smart.tom.ReplicaContext;
import navigators.smart.tom.ServiceReplica;
import navigators.smart.tom.server.DefaultRecoverable;

/**
 *
 * @author sweta
 * 
 * This class will create a ServiceReplica and will initialize
 * it with a implementation of Executable and Recoverable interfaces. 
 */
public class BFTListImpl extends DefaultRecoverable {

    BFTMapList tableList = new BFTMapList();
    ServiceReplica replica = null;
    ReplicaContext replicaCtx;
    
    //The constructor passes the id of the server to the super class
    public BFTListImpl(int id) {
        super();
    	replica = new ServiceReplica(id, this, this);
    }

    public void setReplicaContext(ReplicaContext replicaCtx) {
    	this.replicaCtx = replicaCtx;
    }
    
    public static void main(String[] args){
        if(args.length < 1) {
            System.out.println("Use: java BFTMapImpl <processId>");
            System.exit(-1);
        }
        new BFTListImpl(Integer.parseInt(args[0]));
    }
    
    @Override
    public byte[] getSnapshot() {
        try {

            //System.out.println("[getSnapshot] tables: " + tableMap.getSizeofTable());
            // serialize to byte array and return
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutput out = new ObjectOutputStream(bos);
            out.writeObject(tableList);
            
            out.flush();
            bos.flush();
            out.close();
            bos.close();
            return bos.toByteArray();
        } catch (IOException ex) {
            Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
            return new byte[0];
        }   
    }
    
    //@Override
    /*public byte[] getSnapshot() {
        try {
            Map<String, Map<String, byte[]>> tables = tableMap.getTables();
            Collection<String> tableNames = tables.keySet();
            ByteArrayOutputStream baos = new ByteArrayOutputStream(10000);
            DataOutputStream dos = new DataOutputStream(baos);
            for(String tableName : tableNames) {
                System.out.println("[getSnapshot] Table name: " + tableName);
                dos.writeUTF(tableName);
                Map<String, byte[]> tableTmp = tables.get(tableName);
                dos.writeInt(tableTmp.size());
                for(String key : tableTmp.keySet()) {
                    dos.writeUTF(key);
                    dos.flush();
                    byte[] value = tableTmp.get(key);
                    dos.writeInt(value.length);
                    dos.write(value);
                    dos.flush();
                    System.out.println("[getSnapshot] ---- Size of  key '" + key + "': " + value.length);
                }
                System.out.println("[getSnapshot] ---- Count of rows for table '" + tableName + "': " + tableTmp.size());
                dos.flush();
            }
            byte[] state = baos.toByteArray();
            System.out.println("[getSnapshot] Current byte array size: " + state.length);
            return state;
        } catch (IOException ex) {
            Logger.getLogger(BFTMapImpl.class.getName()).log(Level.SEVERE, null, ex);
            return new byte[0];
        }
    }*/

    @Override
    public void installSnapshot(byte[] state) {
        try {

            // serialize to byte array and return
            ByteArrayInputStream bis = new ByteArrayInputStream(state);
            ObjectInput in = new ObjectInputStream(bis);
            tableList = (BFTMapList) in.readObject();
            in.close();
            bis.close();

        } catch (ClassNotFoundException ex) {
            Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    //@Override
    /*public void installSnapshot(byte[] state) {
        try {
            tableMap = new BFTTableMap();
            ByteArrayInputStream bais = new ByteArrayInputStream(state);
            DataInputStream dis = new DataInputStream(bais);
           
            System.out.println("[installSnapshot] Current byte array size: " + state.length);
            while(dis.available() > 0) {
                Map<String, byte[]> table = new HashMap<String, byte[]>();
                String tableName = dis.readUTF();
                System.out.println("[installSnapshot] Table name: " + tableName);
                tableMap.addTable(tableName, table);
                int tableSize = dis.readInt();
                System.out.println("[installSnapshot] ---- Count of rows for table '" + tableName + "': " + tableSize);
                for(int i = 0; i < tableSize; i++) {
                    String key = dis.readUTF();
                    int valueSize = dis.readInt();
                    byte[] value = new byte[valueSize];
                    dis.read(value, 0, valueSize);
                    System.out.println("[installSnapshot] ---- Size of  key '" + key + "': " + value.length);
                    tableMap.addData(tableName, key, value);
                }
               
            }
        } catch (IOException ex) {
            Logger.getLogger(BFTMapImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
    }*/
    
    @Override
    @SuppressWarnings("static-access")
    public byte[][] executeBatch2(byte[][] commands, MessageContext[] msgCtxs) {
        
        byte [][] replies = new byte[commands.length][];
        for (int i = 0; i < commands.length; i++) {
            
            byte [] command = commands[i];
            
            // O MENSSAGE CONTEXT APARECE A NULO QUANDO SE INSTALA O ESTADO!!!
            //MessageContext msgCtx = msgCtxs[i];
            try {
                ByteArrayInputStream in = new ByteArrayInputStream(command);
                ByteArrayOutputStream out = null;
                byte[] reply = null;
                String listName, value;
                int index;
                List<String> list = null;
                int cmd = new DataInputStream(in).readInt();
                switch (cmd) {
                    //operations on the hashmap
                    case LVRequestType.PUT:
                        listName = new DataInputStream(in).readUTF();
                        value = new DataInputStream(in).readUTF();
                        //String value = new DataInputStream(in).readUTF();
                        //byte[] valueBytes = value.getBytes();
                        //System.out.println("Key received: " + key);
                        out = new ByteArrayOutputStream();
                        boolean added = tableList.addData(listName, value);
                        if (added) System.out.println("added " + listName + " with value " + value);
                        DataOutputStream dout = new DataOutputStream(out);
                        dout.writeBoolean(added);
                        dout.close();
                        out.close();
                        reply = out.toByteArray();
                        System.out.println("array size: " + reply.length);
                        break;
                    case LVRequestType.REMOVE:
                        listName = new DataInputStream(in).readUTF();
                        index = new DataInputStream(in).readInt();
                        System.out.println("Index received: " + index);
                        value = tableList.removeEntry(listName, index);
                        //value = new String(valueBytes);
                        System.out.println("Value removed is : " + value);
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeUTF(value);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    case LVRequestType.LIST_CREATE:
                        listName = new DataInputStream(in).readUTF();
                        //ByteArrayInputStream in1 = new ByteArrayInputStream(command);
                        ObjectInputStream objIn = new ObjectInputStream(in);
                        try {
                            boolean hasList = objIn.readBoolean();
                            if (hasList) list = (List<String>) objIn.readObject();
                            else list = new ArrayList<String>();
                        } catch (ClassNotFoundException ex) {
                            Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        List<String> listCreated = tableList.addList(listName, list);
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        ObjectOutputStream objOut = new ObjectOutputStream(bos);
                        objOut.writeObject(listCreated);
                        objOut.close();
                        in.close();
                        reply = bos.toByteArray();
                        break;
                    case LVRequestType.LIST_REMOVE:
                        listName = new DataInputStream(in).readUTF();
                        list = tableList.removeList(listName);
                        bos = new ByteArrayOutputStream();
                        objOut = new ObjectOutputStream(bos);
                        objOut.writeObject(list);
                        objOut.close();
                        objOut.close();
                        reply = bos.toByteArray();
                        break;


                    case LVRequestType.SIZE_TABLE:
                        int size1 = tableList.getSizeofList();
                        System.out.println("Size " + size1);
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeInt(size1);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    case LVRequestType.GET:
                        listName = new DataInputStream(in).readUTF();
                        System.out.println("tablename: " + listName);
                        index = new DataInputStream(in).readInt();
                        System.out.println("index received: " + index);
                        value = tableList.getEntry(listName, index);
                        //value = new String(valueBytes);
                        System.out.println("The value to be get is: " + value);
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeUTF(value);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    case LVRequestType.SIZE:
                        String tableName2 = new DataInputStream(in).readUTF();
                        int size = tableList.getSize(tableName2);
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeInt(size);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    case LVRequestType.CHECK:
                        listName = new DataInputStream(in).readUTF();
                        index = new DataInputStream(in).readInt();
                        System.out.println("Table Key received: " + index);
                        value = tableList.getEntry(listName, index);
                        boolean entryExists = value != null;
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeBoolean(entryExists);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    case LVRequestType.LIST_CREATE_CHECK:
                        listName = new DataInputStream(in).readUTF();
                        System.out.println("Table of Table Key received: " + listName);
                        list = tableList.getName(listName);
                        boolean tableExists = (list != null);
                        System.out.println("Table exists: " + tableExists);
                        out = new ByteArrayOutputStream();
                        new DataOutputStream(out).writeBoolean(tableExists);
                        reply = out.toByteArray();
                        out.close();
                        break;
                    }
                    replies[i] = reply;
                } catch (IOException ex) {
                    Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
                    return null;
                }
        }
        return replies;
    }

    @SuppressWarnings("static-access")
    public byte[] executeUnordered(byte[] command, MessageContext msgCtx) {
    	try {
	        ByteArrayInputStream in = new ByteArrayInputStream(command);
	        ByteArrayOutputStream out = null;
	        byte[] reply = null;
	        int cmd = new DataInputStream(in).readInt();
                String value;
                int index;
                List<String> list;
	        switch (cmd) {
                case LVRequestType.SIZE_TABLE:
                    int size1 = tableList.getSizeofList();
                    System.out.println("Size " + size1);
                    out = new ByteArrayOutputStream();
                    new DataOutputStream(out).writeInt(size1);
                    reply = out.toByteArray();
                    break;
                case LVRequestType.GET:
                    String tableName = new DataInputStream(in).readUTF();
                    System.out.println("tablename: " + tableName);
                    index = new DataInputStream(in).readInt();
                    System.out.println("Key received: " + index);
                    value = tableList.getEntry(tableName, index);
                    //String value = new String(valueBytes);
                    System.out.println("The value to be get is: " + value);
                    out = new ByteArrayOutputStream();
                    new DataOutputStream(out).writeBytes(value);
                    reply = out.toByteArray();
                    break;
                case LVRequestType.SIZE:
                    String tableName2 = new DataInputStream(in).readUTF();
                    int size = tableList.getSize(tableName2);
                    System.out.println("Size " + size);
                    out = new ByteArrayOutputStream();
                    new DataOutputStream(out).writeInt(size);
                    reply = out.toByteArray();
                    break;
	        case LVRequestType.CHECK:
	            tableName = new DataInputStream(in).readUTF();
	            index = new DataInputStream(in).readInt();
	            System.out.println("Table Key received: " + index);
	            value = tableList.getEntry(tableName, index);
	            boolean entryExists = value != null;
	            out = new ByteArrayOutputStream();
	            new DataOutputStream(out).writeBoolean(entryExists);
	            reply = out.toByteArray();
	            break;
		case LVRequestType.LIST_CREATE_CHECK:
		    tableName = new DataInputStream(in).readUTF();
		    System.out.println("Table of Table Key received: " + tableName);
		    list = tableList.getName(tableName);
		    boolean tableExists = (list != null);
		    System.out.println("Table exists: " + tableExists);
		    out = new ByteArrayOutputStream();
		    new DataOutputStream(out).writeBoolean(tableExists);
		    reply = out.toByteArray();
		    break;
	        }
	        return reply;
	    } catch (IOException ex) {
	        Logger.getLogger(BFTListImpl.class.getName()).log(Level.SEVERE, null, ex);
	        return null;
	    }
    }

}